/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Capabilities.java
 * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
 */

package weka.core;

import weka.core.converters.ConverterUtils.DataSource;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

/**
 * A class that describes the capabilites (e.g., handling certain types of
 * attributes, missing values, types of classes, etc.) of a specific classifier.
 * By default, the classifier is capable of nothing. This ensures that new
 * features have to be enabled explicitly. <p/>
 * 
 * A common code fragment for making use of the capabilities in a classifier
 * would be this:
 * 
 * <pre>
 * public void &lt;b&gt;buildClassifier&lt;/b&gt;(Instances instances) throws Exception {
 *   // can the classifier handle the data?
 *   getCapabilities().&lt;b&gt;testWithFail(instances)&lt;/b&gt;;
 *   ...
 *   // possible deletion of instances with missing class labels, etc.
 * </pre>
 * 
 * For only testing a single attribute, use this:
 * 
 * <pre>
 *   ...
 *   Attribute att = instances.attribute(0);
 *   getCapabilities().&lt;b&gt;testWithFail(att)&lt;/b&gt;;
 *   ...
 * </pre>
 * 
 * Or for testing the class attribute (uses the capabilities that are especially
 * for the class):
 * 
 * <pre>
 *   ...
 *   Attribute att = instances.classAttribute();
 *   getCapabilities().&lt;b&gt;testWithFail(att, &lt;i&gt;true&lt;/i&gt;)&lt;/b&gt;;
 *   ...
 * </pre>
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 1.27 $
 */
public class Capabilities implements Cloneable, Serializable, RevisionHandler {

	/** serialversion UID */
	static final long serialVersionUID = -5478590032325567849L;

	/** the properties file for managing the tests */
	public final static String PROPERTIES_FILE = "weka/core/Capabilities.props";

	/** the actual properties */
	protected static Properties PROPERTIES;

	/** defines an attribute type */
	private final static int ATTRIBUTE = 1;

	/** defines a class type */
	private final static int CLASS = 2;

	/** defines an attribute capability */
	private final static int ATTRIBUTE_CAPABILITY = 4;

	/** defines a class capability */
	private final static int CLASS_CAPABILITY = 8;

	/** defines a other capability */
	private final static int OTHER_CAPABILITY = 16;

	/** enumeration of all capabilities */
	public enum Capability {
		// attributes
		/** can handle nominal attributes */
		NOMINAL_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY,
				"Nominal attributes"),
		/** can handle binary attributes */
		BINARY_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Binary attributes"),
		/** can handle unary attributes */
		UNARY_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Unary attributes"),
		/** can handle empty nominal attributes */
		EMPTY_NOMINAL_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY,
				"Empty nominal attributes"),
		/** can handle numeric attributes */
		NUMERIC_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY,
				"Numeric attributes"),
		/** can handle date attributes */
		DATE_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "Date attributes"),
		/** can handle string attributes */
		STRING_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY, "String attributes"),
		/** can handle relational attributes */
		RELATIONAL_ATTRIBUTES(ATTRIBUTE + ATTRIBUTE_CAPABILITY,
				"Relational attributes"),
		/** can handle missing values in attributes */
		MISSING_VALUES(ATTRIBUTE_CAPABILITY, "Missing values"),
		// class
		/** can handle data without class attribute, eg clusterers */
		NO_CLASS(CLASS_CAPABILITY, "No class"),
		/** can handle nominal classes */
		NOMINAL_CLASS(CLASS + CLASS_CAPABILITY, "Nominal class"),
		/** can handle binary classes */
		BINARY_CLASS(CLASS + CLASS_CAPABILITY, "Binary class"),
		/** can handle unary classes */
		UNARY_CLASS(CLASS + CLASS_CAPABILITY, "Unary class"),
		/** can handle empty nominal classes */
		EMPTY_NOMINAL_CLASS(CLASS + CLASS_CAPABILITY, "Empty nominal class"),
		/** can handle numeric classes */
		NUMERIC_CLASS(CLASS + CLASS_CAPABILITY, "Numeric class"),
		/** can handle date classes */
		DATE_CLASS(CLASS + CLASS_CAPABILITY, "Date class"),
		/** can handle string classes */
		STRING_CLASS(CLASS + CLASS_CAPABILITY, "String class"),
		/** can handle relational classes */
		RELATIONAL_CLASS(CLASS + CLASS_CAPABILITY, "Relational class"),
		/** can handle missing values in class attribute */
		MISSING_CLASS_VALUES(CLASS_CAPABILITY, "Missing class values"),
		// other
		/** can handle multi-instance data */
		ONLY_MULTIINSTANCE(OTHER_CAPABILITY, "Only multi-Instance data");

		/** the flags for the capabilities */
		private int m_Flags = 0;

		/** the display string */
		private String m_Display;

		/**
		 * initializes the capability with the given flags
		 * 
		 * @param flags
		 *            "meta-data" for the capability
		 * @param display
		 *            the display string (must be unique!)
		 */
		private Capability(int flags, String display) {
			m_Flags = flags;
			m_Display = display;
		}

		/**
		 * returns true if the capability is an attribute
		 * 
		 * @return true if the capability is an attribute
		 */
		public boolean isAttribute() {
			return ((m_Flags & ATTRIBUTE) == ATTRIBUTE);
		}

		/**
		 * returns true if the capability is a class
		 * 
		 * @return true if the capability is a class
		 */
		public boolean isClass() {
			return ((m_Flags & CLASS) == CLASS);
		}

		/**
		 * returns true if the capability is an attribute capability
		 * 
		 * @return true if the capability is an attribute capability
		 */
		public boolean isAttributeCapability() {
			return ((m_Flags & ATTRIBUTE_CAPABILITY) == ATTRIBUTE_CAPABILITY);
		}

		/**
		 * returns true if the capability is a class capability
		 * 
		 * @return true if the capability is a class capability
		 */
		public boolean isOtherCapability() {
			return ((m_Flags & OTHER_CAPABILITY) == OTHER_CAPABILITY);
		}

		/**
		 * returns true if the capability is a other capability
		 * 
		 * @return true if the capability is a other capability
		 */
		public boolean isClassCapability() {
			return ((m_Flags & CLASS_CAPABILITY) == CLASS_CAPABILITY);
		}

		/**
		 * returns the display string of the capability
		 * 
		 * @return the display string
		 */
		public String toString() {
			return m_Display;
		}
	};

	/** the object that owns this capabilities instance */
	protected CapabilitiesHandler m_Owner;

	/** the hashset for storing the active capabilities */
	protected HashSet m_Capabilities;

	/** the hashset for storing dependent capabilities, eg for meta-classifiers */
	protected HashSet m_Dependencies;

	/** the reason why the test failed, used to throw an exception */
	protected Exception m_FailReason = null;

	/** the minimum number of instances in a dataset */
	protected int m_MinimumNumberInstances = 1;

	/** whether to perform any tests at all */
	protected boolean m_Test;

	/** whether to perform data based tests */
	protected boolean m_InstancesTest;

	/** whether to perform attribute based tests */
	protected boolean m_AttributeTest;

	/** whether to test for missing values */
	protected boolean m_MissingValuesTest;

	/** whether to test for missing class values */
	protected boolean m_MissingClassValuesTest;

	/** whether to test for minimum number of instances */
	protected boolean m_MinimumNumberInstancesTest;

	/**
	 * initializes the capabilities for the given owner
	 * 
	 * @param owner
	 *            the object that produced this Capabilities instance
	 */
	public Capabilities(CapabilitiesHandler owner) {
		super();

		setOwner(owner);
		m_Capabilities = new HashSet();
		m_Dependencies = new HashSet();

		// load properties
		if (PROPERTIES == null) {
			try {
				PROPERTIES = Utils.readProperties(PROPERTIES_FILE);
			} catch (Exception e) {
				e.printStackTrace();
				PROPERTIES = new Properties();
			}
		}

		m_Test = Boolean.parseBoolean(PROPERTIES.getProperty("Test", "true"));
		m_InstancesTest = Boolean.parseBoolean(PROPERTIES.getProperty(
				"InstancesTest", "true"))
				&& m_Test;
		m_AttributeTest = Boolean.parseBoolean(PROPERTIES.getProperty(
				"AttributeTest", "true"))
				&& m_Test;
		m_MissingValuesTest = Boolean.parseBoolean(PROPERTIES.getProperty(
				"MissingValuesTest", "true"))
				&& m_Test;
		m_MissingClassValuesTest = Boolean.parseBoolean(PROPERTIES.getProperty(
				"MissingClassValuesTest", "true"))
				&& m_Test;
		m_MinimumNumberInstancesTest = Boolean.parseBoolean(PROPERTIES
				.getProperty("MinimumNumberInstancesTest", "true"))
				&& m_Test;
	}

	/**
	 * Creates and returns a copy of this object.
	 * 
	 * @return a clone of this object
	 */
	public Object clone() {
		Capabilities result;

		result = new Capabilities(m_Owner);
		result.assign(this);

		return result;
	}

	/**
	 * retrieves the data from the given Capabilities object
	 * 
	 * @param c
	 *            the capabilities object to initialize with
	 */
	public void assign(Capabilities c) {
		for (Capability cap : Capability.values()) {
			// capability
			if (c.handles(cap))
				enable(cap);
			else
				disable(cap);
			// dependency
			if (c.hasDependency(cap))
				enableDependency(cap);
			else
				disableDependency(cap);
		}

		setMinimumNumberInstances(c.getMinimumNumberInstances());
	}

	/**
	 * performs an AND conjunction with the capabilities of the given
	 * Capabilities object and updates itself
	 * 
	 * @param c
	 *            the capabilities to AND with
	 */
	public void and(Capabilities c) {
		for (Capability cap : Capability.values()) {
			// capability
			if (handles(cap) && c.handles(cap))
				m_Capabilities.add(cap);
			else
				m_Capabilities.remove(cap);
			// dependency
			if (hasDependency(cap) && c.hasDependency(cap))
				m_Dependencies.add(cap);
			else
				m_Dependencies.remove(cap);
		}

		// minimum number of instances that both handlers need at least to work
		if (c.getMinimumNumberInstances() > getMinimumNumberInstances())
			setMinimumNumberInstances(c.getMinimumNumberInstances());
	}

	/**
	 * performs an OR conjunction with the capabilities of the given
	 * Capabilities object and updates itself
	 * 
	 * @param c
	 *            the capabilities to OR with
	 */
	public void or(Capabilities c) {
		for (Capability cap : Capability.values()) {
			// capability
			if (handles(cap) || c.handles(cap))
				m_Capabilities.add(cap);
			else
				m_Capabilities.remove(cap);
			// dependency
			if (hasDependency(cap) || c.hasDependency(cap))
				m_Dependencies.add(cap);
			else
				m_Dependencies.remove(cap);
		}

		if (c.getMinimumNumberInstances() < getMinimumNumberInstances())
			setMinimumNumberInstances(c.getMinimumNumberInstances());
	}

	/**
	 * Returns true if the currently set capabilities support at least all of
	 * the capabiliites of the given Capabilities object (checks only the enum!)
	 * 
	 * @param c
	 *            the capabilities to support at least
	 * @return true if all the requested capabilities are supported
	 */
	public boolean supports(Capabilities c) {
		boolean result;

		result = true;

		for (Capability cap : Capability.values()) {
			if (c.handles(cap) && !handles(cap)) {
				result = false;
				break;
			}
		}

		return result;
	}

	/**
	 * Returns true if the currently set capabilities support (or have a
	 * dependency) at least all of the capabilities of the given Capabilities
	 * object (checks only the enum!)
	 * 
	 * @param c
	 *            the capabilities (or dependencies) to support at least
	 * @return true if all the requested capabilities are supported (or at least
	 *         have a dependency)
	 */
	public boolean supportsMaybe(Capabilities c) {
		boolean result;

		result = true;

		for (Capability cap : Capability.values()) {
			if (c.handles(cap) && !(handles(cap) || hasDependency(cap))) {
				result = false;
				break;
			}
		}

		return result;
	}

	/**
	 * sets the owner of this capabilities object
	 * 
	 * @param value
	 *            the new owner
	 */
	public void setOwner(CapabilitiesHandler value) {
		m_Owner = value;
	}

	/**
	 * returns the owner of this capabilities object
	 * 
	 * @return the current owner of this capabilites object
	 */
	public CapabilitiesHandler getOwner() {
		return m_Owner;
	}

	/**
	 * sets the minimum number of instances that have to be in the dataset
	 * 
	 * @param value
	 *            the minimum number of instances
	 */
	public void setMinimumNumberInstances(int value) {
		if (value >= 0)
			m_MinimumNumberInstances = value;
	}

	/**
	 * returns the minimum number of instances that have to be in the dataset
	 * 
	 * @return the minimum number of instances
	 */
	public int getMinimumNumberInstances() {
		return m_MinimumNumberInstances;
	}

	/**
	 * Returns an Iterator over the stored capabilities
	 * 
	 * @return iterator over the current capabilities
	 */
	public Iterator capabilities() {
		return m_Capabilities.iterator();
	}

	/**
	 * Returns an Iterator over the stored dependencies
	 * 
	 * @return iterator over the current dependencies
	 */
	public Iterator dependencies() {
		return m_Dependencies.iterator();
	}

	/**
	 * enables the given capability. Enabling NOMINAL_ATTRIBUTES also enables
	 * BINARY_ATTRIBUTES, UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES.
	 * Enabling BINARY_ATTRIBUTES also enables UNARY_ATTRIBUTES and
	 * EMPTY_NOMINAL_ATTRIBUTES. Enabling UNARY_ATTRIBUTES also enables
	 * EMPTY_NOMINAL_ATTRIBUTES. But NOMINAL_CLASS only enables BINARY_CLASS,
	 * since normal schemes in Weka don't work with datasets that have only 1
	 * class label (or none).
	 * 
	 * @param c
	 *            the capability to enable
	 */
	public void enable(Capability c) {
		// attributes
		if (c == Capability.NOMINAL_ATTRIBUTES) {
			enable(Capability.BINARY_ATTRIBUTES);
		} else if (c == Capability.BINARY_ATTRIBUTES) {
			enable(Capability.UNARY_ATTRIBUTES);
		} else if (c == Capability.UNARY_ATTRIBUTES) {
			enable(Capability.EMPTY_NOMINAL_ATTRIBUTES);
		}
		// class
		else if (c == Capability.NOMINAL_CLASS) {
			enable(Capability.BINARY_CLASS);
		}

		m_Capabilities.add(c);
	}

	/**
	 * enables the dependency flag for the given capability Enabling
	 * NOMINAL_ATTRIBUTES also enables BINARY_ATTRIBUTES, UNARY_ATTRIBUTES and
	 * EMPTY_NOMINAL_ATTRIBUTES. Enabling BINARY_ATTRIBUTES also enables
	 * UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES. Enabling UNARY_ATTRIBUTES
	 * also enables EMPTY_NOMINAL_ATTRIBUTES. But NOMINAL_CLASS only enables
	 * BINARY_CLASS, since normal schemes in Weka don't work with datasets that
	 * have only 1 class label (or none).
	 * 
	 * @param c
	 *            the capability to enable the dependency flag for
	 */
	public void enableDependency(Capability c) {
		// attributes
		if (c == Capability.NOMINAL_ATTRIBUTES) {
			enableDependency(Capability.BINARY_ATTRIBUTES);
		} else if (c == Capability.BINARY_ATTRIBUTES) {
			enableDependency(Capability.UNARY_ATTRIBUTES);
		} else if (c == Capability.UNARY_ATTRIBUTES) {
			enableDependency(Capability.EMPTY_NOMINAL_ATTRIBUTES);
		}
		// class
		else if (c == Capability.NOMINAL_CLASS) {
			enableDependency(Capability.BINARY_CLASS);
		}

		m_Dependencies.add(c);
	}

	/**
	 * enables all class types
	 * 
	 * @see #disableAllClasses()
	 * @see #getClassCapabilities()
	 */
	public void enableAllClasses() {
		for (Capability cap : Capability.values()) {
			if (cap.isClass())
				enable(cap);
		}
	}

	/**
	 * enables all class type dependencies
	 * 
	 * @see #disableAllClassDependencies()
	 * @see #getClassCapabilities()
	 */
	public void enableAllClassDependencies() {
		for (Capability cap : Capability.values()) {
			if (cap.isClass())
				enableDependency(cap);
		}
	}

	/**
	 * enables all attribute types
	 * 
	 * @see #disableAllAttributes()
	 * @see #getAttributeCapabilities()
	 */
	public void enableAllAttributes() {
		for (Capability cap : Capability.values()) {
			if (cap.isAttribute())
				enable(cap);
		}
	}

	/**
	 * enables all attribute type dependencies
	 * 
	 * @see #disableAllAttributeDependencies()
	 * @see #getAttributeCapabilities()
	 */
	public void enableAllAttributeDependencies() {
		for (Capability cap : Capability.values()) {
			if (cap.isAttribute())
				enableDependency(cap);
		}
	}

	/**
	 * disables the given capability Disabling NOMINAL_ATTRIBUTES also disables
	 * BINARY_ATTRIBUTES, UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES.
	 * Disabling BINARY_ATTRIBUTES also disables UNARY_ATTRIBUTES and
	 * EMPTY_NOMINAL_ATTRIBUTES. Disabling UNARY_ATTRIBUTES also disables
	 * EMPTY_NOMINAL_ATTRIBUTES. The same hierarchy applies to the class
	 * capabilities.
	 * 
	 * @param c
	 *            the capability to disable
	 */
	public void disable(Capability c) {
		// attributes
		if (c == Capability.NOMINAL_ATTRIBUTES) {
			disable(Capability.BINARY_ATTRIBUTES);
		} else if (c == Capability.BINARY_ATTRIBUTES) {
			disable(Capability.UNARY_ATTRIBUTES);
		} else if (c == Capability.UNARY_ATTRIBUTES) {
			disable(Capability.EMPTY_NOMINAL_ATTRIBUTES);
		}
		// class
		else if (c == Capability.NOMINAL_CLASS) {
			disable(Capability.BINARY_CLASS);
		} else if (c == Capability.BINARY_CLASS) {
			disable(Capability.UNARY_CLASS);
		} else if (c == Capability.UNARY_CLASS) {
			disable(Capability.EMPTY_NOMINAL_CLASS);
		}

		m_Capabilities.remove(c);
	}

	/**
	 * disables the dependency of the given capability Disabling
	 * NOMINAL_ATTRIBUTES also disables BINARY_ATTRIBUTES, UNARY_ATTRIBUTES and
	 * EMPTY_NOMINAL_ATTRIBUTES. Disabling BINARY_ATTRIBUTES also disables
	 * UNARY_ATTRIBUTES and EMPTY_NOMINAL_ATTRIBUTES. Disabling UNARY_ATTRIBUTES
	 * also disables EMPTY_NOMINAL_ATTRIBUTES. The same hierarchy applies to the
	 * class capabilities.
	 * 
	 * @param c
	 *            the capability to disable the dependency flag for
	 */
	public void disableDependency(Capability c) {
		// attributes
		if (c == Capability.NOMINAL_ATTRIBUTES) {
			disableDependency(Capability.BINARY_ATTRIBUTES);
		} else if (c == Capability.BINARY_ATTRIBUTES) {
			disableDependency(Capability.UNARY_ATTRIBUTES);
		} else if (c == Capability.UNARY_ATTRIBUTES) {
			disableDependency(Capability.EMPTY_NOMINAL_ATTRIBUTES);
		}
		// class
		else if (c == Capability.NOMINAL_CLASS) {
			disableDependency(Capability.BINARY_CLASS);
		} else if (c == Capability.BINARY_CLASS) {
			disableDependency(Capability.UNARY_CLASS);
		} else if (c == Capability.UNARY_CLASS) {
			disableDependency(Capability.EMPTY_NOMINAL_CLASS);
		}

		m_Dependencies.remove(c);
	}

	/**
	 * disables all class types
	 * 
	 * @see #enableAllClasses()
	 * @see #getClassCapabilities()
	 */
	public void disableAllClasses() {
		for (Capability cap : Capability.values()) {
			if (cap.isClass())
				disable(cap);
		}
	}

	/**
	 * disables all class type dependencies
	 * 
	 * @see #enableAllClassDependencies()
	 * @see #getClassCapabilities()
	 */
	public void disableAllClassDependencies() {
		for (Capability cap : Capability.values()) {
			if (cap.isClass())
				disableDependency(cap);
		}
	}

	/**
	 * disables all attribute types
	 * 
	 * @see #enableAllAttributes()
	 * @see #getAttributeCapabilities()
	 */
	public void disableAllAttributes() {
		for (Capability cap : Capability.values()) {
			if (cap.isAttribute())
				disable(cap);
		}
	}

	/**
	 * disables all attribute type dependencies
	 * 
	 * @see #enableAllAttributeDependencies()
	 * @see #getAttributeCapabilities()
	 */
	public void disableAllAttributeDependencies() {
		for (Capability cap : Capability.values()) {
			if (cap.isAttribute())
				disableDependency(cap);
		}
	}

	/**
	 * returns all class capabilities
	 * 
	 * @return all capabilities regarding the class
	 * @see #enableAllClasses()
	 * @see #disableAllClasses()
	 */
	public Capabilities getClassCapabilities() {
		Capabilities result;

		result = new Capabilities(getOwner());

		for (Capability cap : Capability.values()) {
			if (cap.isClassCapability()) {
				if (handles(cap))
					result.m_Capabilities.add(cap);
			}
		}

		return result;
	}

	/**
	 * returns all attribute capabilities
	 * 
	 * @return all capabilities regarding attributes
	 * @see #enableAllAttributes()
	 * @see #disableAllAttributes()
	 */
	public Capabilities getAttributeCapabilities() {
		Capabilities result;

		result = new Capabilities(getOwner());

		for (Capability cap : Capability.values()) {
			if (cap.isAttributeCapability()) {
				if (handles(cap))
					result.m_Capabilities.add(cap);
			}
		}

		return result;
	}

	/**
	 * returns all other capabilities, besides class and attribute related ones
	 * 
	 * @return all other capabilities, besides class and attribute related ones
	 */
	public Capabilities getOtherCapabilities() {
		Capabilities result;

		result = new Capabilities(getOwner());

		for (Capability cap : Capability.values()) {
			if (cap.isOtherCapability()) {
				if (handles(cap))
					result.m_Capabilities.add(cap);
			}
		}

		return result;
	}

	/**
	 * returns true if the classifier handler has the specified capability
	 * 
	 * @param c
	 *            the capability to test
	 * @return true if the classifier handler has the capability
	 */
	public boolean handles(Capability c) {
		return m_Capabilities.contains(c);
	}

	/**
	 * returns true if the classifier handler has a dependency for the specified
	 * capability
	 * 
	 * @param c
	 *            the capability to test
	 * @return true if the classifier handler has a dependency for the
	 *         capability
	 */
	public boolean hasDependency(Capability c) {
		return m_Dependencies.contains(c);
	}

	/**
	 * Checks whether there are any dependencies at all
	 * 
	 * @return true if there is at least one dependency for a capability
	 */
	public boolean hasDependencies() {
		return (m_Dependencies.size() > 0);
	}

	/**
	 * returns the reason why the tests failed, is null if tests succeeded
	 * 
	 * @return the reason why the tests failed
	 */
	public Exception getFailReason() {
		return m_FailReason;
	}

	/**
	 * Generates the message for, e.g., an exception. Adds the classname before
	 * the actual message and returns that string.
	 * 
	 * @param msg
	 *            the actual content of the message, e.g., exception
	 * @return the new message
	 */
	protected String createMessage(String msg) {
		String result;

		result = "";

		if (getOwner() != null)
			result = getOwner().getClass().getName();
		else
			result = "<anonymous>";

		result += ": " + msg;

		return result;
	}

	/**
	 * Test the given attribute, whether it can be processed by the handler,
	 * given its capabilities. The method assumes that the specified attribute
	 * is not the class attribute.
	 * 
	 * @param att
	 *            the attribute to test
	 * @return true if all the tests succeeded
	 * @see #test(Attribute, boolean)
	 */
	public boolean test(Attribute att) {
		return test(att, false);
	}

	/**
	 * Test the given attribute, whether it can be processed by the handler,
	 * given its capabilities.
	 * 
	 * @param att
	 *            the attribute to test
	 * @param isClass
	 *            whether this attribute is the class attribute
	 * @return true if all the tests succeeded
	 * @see #m_AttributeTest
	 */
	public boolean test(Attribute att, boolean isClass) {
		boolean result;
		Capability cap;
		Capability capBinary;
		Capability capUnary;
		Capability capEmpty;
		String errorStr;

		result = true;

		// shall we test the data?
		if (!m_AttributeTest)
			return result;

		// for exception
		if (isClass)
			errorStr = "class";
		else
			errorStr = "attributes";

		switch (att.type()) {
		case Attribute.NOMINAL:
			if (isClass) {
				cap = Capability.NOMINAL_CLASS;
				capBinary = Capability.BINARY_CLASS;
				capUnary = Capability.UNARY_CLASS;
				capEmpty = Capability.EMPTY_NOMINAL_CLASS;
			} else {
				cap = Capability.NOMINAL_ATTRIBUTES;
				capBinary = Capability.BINARY_ATTRIBUTES;
				capUnary = Capability.UNARY_ATTRIBUTES;
				capEmpty = Capability.EMPTY_NOMINAL_ATTRIBUTES;
			}

			if (handles(cap) && (att.numValues() > 2))
				break;
			else if (handles(capBinary) && (att.numValues() == 2))
				break;
			else if (handles(capUnary) && (att.numValues() == 1))
				break;
			else if (handles(capEmpty) && (att.numValues() == 0))
				break;

			if (att.numValues() == 0) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle empty nominal " + errorStr
								+ "!"));
				result = false;
			}
			if (att.numValues() == 1) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle unary " + errorStr + "!"));
				result = false;
			} else if (att.numValues() == 2) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle binary " + errorStr + "!"));
				result = false;
			} else {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle multi-valued nominal "
								+ errorStr + "!"));
				result = false;
			}
			break;

		case Attribute.NUMERIC:
			if (isClass)
				cap = Capability.NUMERIC_CLASS;
			else
				cap = Capability.NUMERIC_ATTRIBUTES;

			if (!handles(cap)) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle numeric " + errorStr + "!"));
				result = false;
			}
			break;

		case Attribute.DATE:
			if (isClass)
				cap = Capability.DATE_CLASS;
			else
				cap = Capability.DATE_ATTRIBUTES;

			if (!handles(cap)) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle date " + errorStr + "!"));
				result = false;
			}
			break;

		case Attribute.STRING:
			if (isClass)
				cap = Capability.STRING_CLASS;
			else
				cap = Capability.STRING_ATTRIBUTES;

			if (!handles(cap)) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle string " + errorStr + "!"));
				result = false;
			}
			break;

		case Attribute.RELATIONAL:
			if (isClass)
				cap = Capability.RELATIONAL_CLASS;
			else
				cap = Capability.RELATIONAL_ATTRIBUTES;

			if (!handles(cap)) {
				m_FailReason = new UnsupportedAttributeTypeException(
						createMessage("Cannot handle relational " + errorStr
								+ "!"));
				result = false;
			}
			// attributes in the relation of this attribute must be tested
			// separately with a different Capabilites object
			break;

		default:
			m_FailReason = new UnsupportedAttributeTypeException(
					createMessage("Cannot handle unknown attribute type '"
							+ att.type() + "'!"));
			result = false;
		}

		return result;
	}

	/**
	 * Tests the given data, whether it can be processed by the handler, given
	 * its capabilities. Classifiers implementing the
	 * <code>MultiInstanceCapabilitiesHandler</code> interface are checked
	 * automatically for their multi-instance Capabilities (if no bags, then
	 * only the bag-structure, otherwise only the first bag).
	 * 
	 * @param data
	 *            the data to test
	 * @return true if all the tests succeeded
	 * @see #test(Instances, int, int)
	 */
	public boolean test(Instances data) {
		return test(data, 0, data.numAttributes() - 1);
	}

	/**
	 * Tests a certain range of attributes of the given data, whether it can be
	 * processed by the handler, given its capabilities. Classifiers
	 * implementing the <code>MultiInstanceCapabilitiesHandler</code>
	 * interface are checked automatically for their multi-instance Capabilities
	 * (if no bags, then only the bag-structure, otherwise only the first bag).
	 * 
	 * @param data
	 *            the data to test
	 * @param fromIndex
	 *            the range of attributes - start (incl.)
	 * @param toIndex
	 *            the range of attributes - end (incl.)
	 * @return true if all the tests succeeded
	 * @see MultiInstanceCapabilitiesHandler
	 * @see #m_InstancesTest
	 * @see #m_MissingValuesTest
	 * @see #m_MissingClassValuesTest
	 * @see #m_MinimumNumberInstancesTest
	 */
	public boolean test(Instances data, int fromIndex, int toIndex) {
		int i;
		int n;
		int m;
		Attribute att;
		Instance inst;
		boolean testClass;
		Capabilities cap;
		boolean missing;
		Iterator iter;

		// shall we test the data?
		if (!m_InstancesTest)
			return true;

		// no Capabilities? -> warning
		if ((m_Capabilities.size() == 0)
				|| ((m_Capabilities.size() == 1) && handles(Capability.NO_CLASS)))
			System.err.println(createMessage("No capabilities set!"));

		// any attributes?
		if (toIndex - fromIndex < 0) {
			m_FailReason = new WekaException(createMessage("No attributes!"));
			return false;
		}

		// do wee need to test the class attribute, i.e., is the class attribute
		// within the range of attributes?
		testClass = (data.classIndex() > -1)
				&& (data.classIndex() >= fromIndex)
				&& (data.classIndex() <= toIndex);

		// attributes
		for (i = fromIndex; i <= toIndex; i++) {
			att = data.attribute(i);

			// class is handled separately
			if (i == data.classIndex())
				continue;

			// check attribute types
			if (!test(att))
				return false;
		}

		// class
		if (!handles(Capability.NO_CLASS) && (data.classIndex() == -1)) {
			m_FailReason = new UnassignedClassException(
					createMessage("Class attribute not set!"));
			return false;
		}

		// special case: no class attribute can be handled
		if (handles(Capability.NO_CLASS) && (data.classIndex() > -1)) {
			cap = getClassCapabilities();
			cap.disable(Capability.NO_CLASS);
			iter = cap.capabilities();
			if (!iter.hasNext()) {
				m_FailReason = new WekaException(
						createMessage("Cannot handle any class attribute!"));
				return false;
			}
		}

		if (testClass && !handles(Capability.NO_CLASS)) {
			att = data.classAttribute();
			if (!test(att, true))
				return false;

			// special handling of RELATIONAL class
			// TODO: store additional Capabilities for this case

			// missing class labels
			if (m_MissingClassValuesTest) {
				if (!handles(Capability.MISSING_CLASS_VALUES)) {
					for (i = 0; i < data.numInstances(); i++) {
						if (data.instance(i).classIsMissing()) {
							m_FailReason = new WekaException(
									createMessage("Cannot handle missing class values!"));
							return false;
						}
					}
				} else {
					if (m_MinimumNumberInstancesTest) {
						int hasClass = 0;

						for (i = 0; i < data.numInstances(); i++) {
							if (!data.instance(i).classIsMissing())
								hasClass++;
						}

						// not enough instances with class labels?
						if (hasClass < getMinimumNumberInstances()) {
							m_FailReason = new WekaException(
									createMessage("Not enough training instances with class labels (required: "
											+ getMinimumNumberInstances()
											+ ", provided: " + hasClass + ")!"));
							return false;
						}
					}
				}
			}
		}

		// missing values
		if (m_MissingValuesTest) {
			if (!handles(Capability.MISSING_VALUES)) {
				missing = false;
				for (i = 0; i < data.numInstances(); i++) {
					inst = data.instance(i);

					if (inst instanceof SparseInstance) {
						for (m = 0; m < inst.numValues(); m++) {
							n = inst.index(m);

							// out of scope?
							if (n < fromIndex)
								continue;
							if (n > toIndex)
								break;

							// skip class
							if (n == inst.classIndex())
								continue;

							if (inst.isMissing(n)) {
								missing = true;
								break;
							}
						}
					} else {
						for (n = fromIndex; n <= toIndex; n++) {
							// skip class
							if (n == inst.classIndex())
								continue;

							if (inst.isMissing(n)) {
								missing = true;
								break;
							}
						}
					}

					if (missing) {
						m_FailReason = new NoSupportForMissingValuesException(
								createMessage("Cannot handle missing values!"));
						return false;
					}
				}
			}
		}

		// instances
		if (m_MinimumNumberInstancesTest) {
			if (data.numInstances() < getMinimumNumberInstances()) {
				m_FailReason = new WekaException(
						createMessage("Not enough training instances (required: "
								+ getMinimumNumberInstances()
								+ ", provided: "
								+ data.numInstances() + ")!"));
				return false;
			}
		}

		// Multi-Instance? -> check structure (regardless of attribute range!)
		if (handles(Capability.ONLY_MULTIINSTANCE)) {
			// number of attributes?
			if (data.numAttributes() != 3) {
				m_FailReason = new WekaException(
						createMessage("Incorrect Multi-Instance format, must be 'bag-id, bag, class'!"));
				return false;
			}

			// type of attributes and position of class?
			if (!data.attribute(0).isNominal()
					|| !data.attribute(1).isRelationValued()
					|| (data.classIndex() != data.numAttributes() - 1)) {
				m_FailReason = new WekaException(
						createMessage("Incorrect Multi-Instance format, must be 'NOMINAL att, RELATIONAL att, CLASS att'!"));
				return false;
			}

			// check data immediately
			if (getOwner() instanceof MultiInstanceCapabilitiesHandler) {
				MultiInstanceCapabilitiesHandler handler = (MultiInstanceCapabilitiesHandler) getOwner();
				cap = handler.getMultiInstanceCapabilities();
				boolean result;
				if (data.numInstances() > 0)
					result = cap.test(data.attribute(1).relation(0));
				else
					result = cap.test(data.attribute(1).relation());

				if (!result) {
					m_FailReason = cap.m_FailReason;
					return false;
				}
			}
		}

		// passed all tests!
		return true;
	}

	/**
	 * tests the given attribute by calling the test(Attribute,boolean) method
	 * and throws an exception if the test fails. The method assumes that the
	 * specified attribute is not the class attribute.
	 * 
	 * @param att
	 *            the attribute to test
	 * @throws Exception
	 *             in case the attribute doesn't pass the tests
	 * @see #test(Attribute,boolean)
	 */
	public void testWithFail(Attribute att) throws Exception {
		test(att, false);
	}

	/**
	 * tests the given attribute by calling the test(Attribute,boolean) method
	 * and throws an exception if the test fails.
	 * 
	 * @param att
	 *            the attribute to test
	 * @param isClass
	 *            whether this attribute is the class attribute
	 * @throws Exception
	 *             in case the attribute doesn't pass the tests
	 * @see #test(Attribute,boolean)
	 */
	public void testWithFail(Attribute att, boolean isClass) throws Exception {
		if (!test(att, isClass))
			throw m_FailReason;
	}

	/**
	 * tests the given data by calling the test(Instances,int,int) method and
	 * throws an exception if the test fails.
	 * 
	 * @param data
	 *            the data to test
	 * @param fromIndex
	 *            the range of attributes - start (incl.)
	 * @param toIndex
	 *            the range of attributes - end (incl.)
	 * @throws Exception
	 *             in case the data doesn't pass the tests
	 * @see #test(Instances,int,int)
	 */
	public void testWithFail(Instances data, int fromIndex, int toIndex)
			throws Exception {
		if (!test(data, fromIndex, toIndex))
			throw m_FailReason;
	}

	/**
	 * tests the given data by calling the test(Instances) method and throws an
	 * exception if the test fails.
	 * 
	 * @param data
	 *            the data to test
	 * @throws Exception
	 *             in case the data doesn't pass the tests
	 * @see #test(Instances)
	 */
	public void testWithFail(Instances data) throws Exception {
		if (!test(data))
			throw m_FailReason;
	}

	/**
	 * returns a string representation of the capabilities
	 * 
	 * @return a string representation of this object
	 */
	public String toString() {
		Vector sorted;
		StringBuffer result;

		result = new StringBuffer();

		// capabilities
		sorted = new Vector(m_Capabilities);
		Collections.sort(sorted);
		result.append("Capabilities: " + sorted.toString() + "\n");

		// dependencies
		sorted = new Vector(m_Dependencies);
		Collections.sort(sorted);
		result.append("Dependencies: " + sorted.toString() + "\n");

		// other stuff
		result.append("min # Instance: " + getMinimumNumberInstances() + "\n");

		return result.toString();
	}

	/**
	 * turns the capabilities object into source code. The returned source code
	 * is a block that creates a Capabilities object named 'objectname' and
	 * enables all the capabilities of this Capabilities object.
	 * 
	 * @param objectname
	 *            the name of the Capabilities object being instantiated
	 * @return the generated source code
	 */
	public String toSource(String objectname) {
		return toSource(objectname, 0);
	}

	/**
	 * turns the capabilities object into source code. The returned source code
	 * is a block that creates a Capabilities object named 'objectname' and
	 * enables all the capabilities of this Capabilities object.
	 * 
	 * @param objectname
	 *            the name of the Capabilities object being instantiated
	 * @param indent
	 *            the number of blanks to indent
	 * @return the generated source code
	 */
	public String toSource(String objectname, int indent) {
		StringBuffer result;
		String capsName;
		String capName;
		String indentStr;
		int i;

		result = new StringBuffer();

		capsName = Capabilities.class.getName();
		capName = Capabilities.Capability.class.getName()
				.replaceAll("\\$", ".");

		indentStr = "";
		for (i = 0; i < indent; i++)
			indentStr += " ";

		// object name
		result.append(indentStr + capsName + " " + objectname + " = new "
				+ capsName + "(this);\n");

		// capabilities
		result.append("\n");
		for (Capability cap : Capability.values()) {
			// capability
			if (handles(cap))
				result.append(indentStr + objectname + ".enable(" + capName
						+ "." + cap.name() + ");\n");
			// dependency
			if (hasDependency(cap))
				result.append(indentStr + objectname + ".enableDependency("
						+ capName + "." + cap.name() + ");\n");
		}

		// other
		result.append("\n");
		result.append(indentStr + objectname + ".setMinimumNumberInstances("
				+ getMinimumNumberInstances() + ");\n");

		result.append("\n");

		return result.toString();
	}

	/**
	 * returns a Capabilities object specific for this data. The multi-instance
	 * capability is not checked as well as the minimum number of instances is
	 * not set.
	 * 
	 * @param data
	 *            the data to base the capabilities on
	 * @return a data-specific capabilities object
	 * @throws Exception
	 *             in case an error occurrs, e.g., an unknown attribute type
	 */
	public static Capabilities forInstances(Instances data) throws Exception {
		return forInstances(data, false);
	}

	/**
	 * returns a Capabilities object specific for this data. The minimum number
	 * of instances is not set, the check for multi-instance data is optional.
	 * 
	 * @param data
	 *            the data to base the capabilities on
	 * @param multi
	 *            if true then the structure is checked, too
	 * @return a data-specific capabilities object
	 * @throws Exception
	 *             in case an error occurrs, e.g., an unknown attribute type
	 */
	public static Capabilities forInstances(Instances data, boolean multi)
			throws Exception {
		Capabilities result;
		Capabilities multiInstance;
		int i;
		int n;
		int m;
		Instance inst;
		boolean missing;

		result = new Capabilities(null);

		// class
		if (data.classIndex() == -1) {
			result.enable(Capability.NO_CLASS);
		} else {
			switch (data.classAttribute().type()) {
			case Attribute.NOMINAL:
				if (data.classAttribute().numValues() == 1)
					result.enable(Capability.UNARY_CLASS);
				else if (data.classAttribute().numValues() == 2)
					result.enable(Capability.BINARY_CLASS);
				else
					result.enable(Capability.NOMINAL_CLASS);
				break;

			case Attribute.NUMERIC:
				result.enable(Capability.NUMERIC_CLASS);
				break;

			case Attribute.STRING:
				result.enable(Capability.STRING_CLASS);
				break;

			case Attribute.DATE:
				result.enable(Capability.DATE_CLASS);
				break;

			case Attribute.RELATIONAL:
				result.enable(Capability.RELATIONAL_CLASS);
				break;

			default:
				throw new UnsupportedAttributeTypeException(
						"Unknown class attribute type '"
								+ data.classAttribute() + "'!");
			}

			// missing class values
			for (i = 0; i < data.numInstances(); i++) {
				if (data.instance(i).classIsMissing()) {
					result.enable(Capability.MISSING_CLASS_VALUES);
					break;
				}
			}
		}

		// attributes
		for (i = 0; i < data.numAttributes(); i++) {
			// skip class
			if (i == data.classIndex())
				continue;

			switch (data.attribute(i).type()) {
			case Attribute.NOMINAL:
				result.enable(Capability.UNARY_ATTRIBUTES);
				if (data.attribute(i).numValues() == 2)
					result.enable(Capability.BINARY_ATTRIBUTES);
				else if (data.attribute(i).numValues() > 2)
					result.enable(Capability.NOMINAL_ATTRIBUTES);
				break;

			case Attribute.NUMERIC:
				result.enable(Capability.NUMERIC_ATTRIBUTES);
				break;

			case Attribute.DATE:
				result.enable(Capability.DATE_ATTRIBUTES);
				break;

			case Attribute.STRING:
				result.enable(Capability.STRING_ATTRIBUTES);
				break;

			case Attribute.RELATIONAL:
				result.enable(Capability.RELATIONAL_ATTRIBUTES);
				break;

			default:
				throw new UnsupportedAttributeTypeException(
						"Unknown attribute type '" + data.attribute(i).type()
								+ "'!");
			}
		}

		// missing values
		missing = false;
		for (i = 0; i < data.numInstances(); i++) {
			inst = data.instance(i);

			if (inst instanceof SparseInstance) {
				for (m = 0; m < inst.numValues(); m++) {
					n = inst.index(m);

					// skip class
					if (n == inst.classIndex())
						continue;

					if (inst.isMissing(n)) {
						missing = true;
						break;
					}
				}
			} else {
				for (n = 0; n < data.numAttributes(); n++) {
					// skip class
					if (n == inst.classIndex())
						continue;

					if (inst.isMissing(n)) {
						missing = true;
						break;
					}
				}
			}

			if (missing) {
				result.enable(Capability.MISSING_VALUES);
				break;
			}
		}

		// multi-instance data?
		if (multi) {
			if ((data.numAttributes() == 3) && (data.attribute(0).isNominal()) // bag-id
					&& (data.attribute(1).isRelationValued()) // bag
					&& (data.classIndex() == data.numAttributes() - 1)) {
				multiInstance = new Capabilities(null);
				multiInstance.or(result.getClassCapabilities());
				multiInstance.enable(Capability.NOMINAL_ATTRIBUTES);
				multiInstance.enable(Capability.RELATIONAL_ATTRIBUTES);
				multiInstance.enable(Capability.ONLY_MULTIINSTANCE);
				result.assign(multiInstance);
			}
		}

		return result;
	}

	/**
	 * loads the given dataset and prints the Capabilities necessary to process
	 * it. <p/>
	 * 
	 * Valid parameters: <p/>
	 * 
	 * -file filename <br/> the file to load
	 * 
	 * -c index the explicit index of the class attribute (default: none)
	 * 
	 * @param args
	 *            the commandline arguments
	 * @throws Exception
	 *             if something goes wrong
	 */
	public static void main(String[] args) throws Exception {
		String tmpStr;
		String filename;
		DataSource source;
		Instances data;
		int classIndex;
		Capabilities cap;
		Iterator iter;

		if (args.length == 0) {
			System.out.println("\nUsage: " + Capabilities.class.getName()
					+ " -file <dataset> [-c <class index>]\n");
			return;
		}

		// get parameters
		tmpStr = Utils.getOption("file", args);
		if (tmpStr.length() == 0)
			throw new Exception("No file provided with option '-file'!");
		else
			filename = tmpStr;

		tmpStr = Utils.getOption("c", args);
		if (tmpStr.length() != 0) {
			if (tmpStr.equals("first"))
				classIndex = 0;
			else if (tmpStr.equals("last"))
				classIndex = -2; // last
			else
				classIndex = Integer.parseInt(tmpStr) - 1;
		} else {
			classIndex = -3; // not set
		}

		// load data
		source = new DataSource(filename);
		if (classIndex == -3)
			data = source.getDataSet();
		else if (classIndex == -2)
			data = source.getDataSet(source.getStructure().numAttributes() - 1);
		else
			data = source.getDataSet(classIndex);

		// determine and print capabilities
		cap = forInstances(data);
		System.out.println("File: " + filename);
		System.out.println("Class index: "
				+ ((data.classIndex() == -1) ? "not set" : ""
						+ (data.classIndex() + 1)));
		System.out.println("Capabilities:");
		iter = cap.capabilities();
		while (iter.hasNext())
			System.out.println("- " + iter.next());
	}

	/**
	 * Returns the revision string.
	 * 
	 * @return the revision
	 */
	public String getRevision() {
		return RevisionUtils.extract("$Revision: 1.27 $");
	}
}
